import java.io.*;
import java.util.*;

/**
 * Summary description for Program
 * @author Yevgeny(Jack) Keyser
 * 
 * CS4320: Software Processes
 * Project 4
 * 
 * @version 10.30.2006
 */
public class TCGen
{
	public static void main(String[] args) throws Exception
	{
		InputStream is = new FileInputStream("statechart.properties");
		StateChartInputStream scis = new StateChartInputStream(is);
		TransProbMatrix tpm = scis.getTransProbMatrix();

		System.out.println("Printing transition prob matrix...");
		System.out.println(tpm);

		StateMachine sm = new StateMachine(tpm);
		TestSuite suite = new TestSuite();

		System.out.print("How many test cases would you like? ");
		LineNumberReader lnr = new LineNumberReader(new InputStreamReader(System.in));
		int numTestCases = Integer.parseInt(lnr.readLine());
		lnr.close();
		System.out.println();

		TestSuiteGeneratorFacade gen = new TestSuiteGeneratorFacade(suite, sm, numTestCases);
		gen.generate();

		System.out.println("Generating test suite...");

		TestSuiteViewHelper tsview = new TestSuiteViewHelper(suite);

		File testSuiteFile = new File("testsuite.html");

	
		testSuiteFile.createNewFile();
		FileOutputStream fos = new FileOutputStream(testSuiteFile);
		tsview.displayTestSuite(fos);
		System.out.println("Rendering test suite...");
	}
}


class TestCase
{
	private List path;

	TestCase(final List path)
	{
		this.path = path;
	}

	Iterator traversePath()
	{
		return path.iterator();
	}
}

class TestSuite
{
	private List testCases;

	TestSuite()
	{
		testCases = new LinkedList();
	}

	void addTestCase(TestCase tc)
	{
		testCases.add(tc);
	}

	Iterator iterateTestCases()
	{
		return testCases.iterator();
	}
}


class TransProbMatrix 
{
	private Hashtable matrix;

	TransProbMatrix()
	{
		matrix = new Hashtable();
	}

	void addProbAt(String row, String col, double prob)
	{
		if (!matrix.containsKey(row))
			matrix.put(row, new Hashtable());

		Hashtable cols = (Hashtable)matrix.get(row);
		cols.put(col, new Double(prob));
	}

	double getProbAt(String row, String col) throws Exception
	{
		if (!matrix.containsKey(row))
			throw new Exception("row not found");

		Hashtable cols = (Hashtable)matrix.get(row);

		return ((Double)cols.get(col)).doubleValue();
	}

	Enumeration getColLabels(String row)
	{
		return ((Hashtable)matrix.get(row)).keys();
	}

	public String toString()
	{
		Enumeration rows = matrix.keys();
		String show = "";

		String rowLabel = (String)rows.nextElement();

		while (rows.hasMoreElements())
		{
			Hashtable cols = (Hashtable)matrix.get(rowLabel);
			Enumeration colLabels = cols.keys();
			String colLabel = (String)colLabels.nextElement();

			while (colLabels.hasMoreElements())
			{
				show+= cols.get(colLabel)+" ";
				colLabel = (String)colLabels.nextElement();
			}

			show += '\n';
			rowLabel = (String)rows.nextElement();
		}

		return show;
	}
}

class StateChartInputStream
{
	private Properties stateChart;
	private TransProbMatrix matrix;

	public StateChartInputStream(InputStream is) throws Exception
	{
		stateChart = new Properties();
		stateChart.load(is);
		matrix = new TransProbMatrix();

		populateMatrix();
	}

	private void populateMatrix() throws Exception
	{
		Enumeration keys = stateChart.propertyNames();

		while (keys.hasMoreElements())
		{
			String key = (String)keys.nextElement();

			if (key.indexOf(",") == -1 || key.indexOf(":")>=0 || key.indexOf("=")>=0)
				throw new Exception("Key invalid for key: " + key);

			String row = key.substring(0, key.indexOf(","));
			String col = key.substring(key.indexOf(",")+1);
			String value = stateChart.getProperty(key);
			matrix.addProbAt(row, col, Double.parseDouble(value));
		}
	}

	TransProbMatrix getTransProbMatrix()
	{
		return matrix;
	}
}

class TestSuiteViewHelper
{
	private TestSuite ts;
	private PrintWriter pw;

	TestSuiteViewHelper(final TestSuite ts)
	{
		this.ts = ts;
	}

	void displayTestSuite(OutputStream os)
	{
		pw = new PrintWriter(os, true);

		pw.println("<html>");
		pw.println("<head>");
		pw.println("<title> CS6300 - P4 - Test plan </title>");
		pw.println("</head>");
		pw.println("<body>");
		pw.println("<table width=100% height=100% align=center>");
		pw.println("<tr><td valign=middle align=center>");
		pw.println("<table width=400 align=center style=\"border:solid; border-color:#000033\" bgcolor=\"#CCCCCC\" border=1>");

		fillCore();

		pw.println("</table>");
		pw.println("</td></tr>");
		pw.println("</table>");
		pw.println("</body>");
		pw.println("</html>");

	}

	private void fillCore()
	{
		Iterator testCaseIter = ts.iterateTestCases();
		TestCase testcase = (TestCase)testCaseIter.next();

		while (testCaseIter.hasNext())
		{
			pw.println("<tr>");
			fillRecord(testcase);
			pw.println("</tr>");
			testcase = (TestCase)testCaseIter.next();
		}
	}

	private void fillRecord(TestCase tc)
	{
		Iterator pathIter = tc.traversePath();
		String stateLabel = (String)pathIter.next();

		while (pathIter.hasNext())
		{
			pw.print("<td align=center valign=middle>" + stateLabel + "</td>");	
			stateLabel = (String)pathIter.next();
		}
	}
}

class StateMachine
{
	private TransProbMatrix tpm;
	private String currentState;
	private LinkedList path;

	StateMachine(final TransProbMatrix tpm) throws Exception
	{
		this.tpm=tpm;
		restart();
	}

	void restart() 
	{
		currentState = "Init";
		path = new LinkedList();	// avoid loss of test case. Clone only does shallow copy which clear can affect.
		path.addLast(currentState);
	}

	String getCurrentStateLabel() throws Exception
	{
		return currentState;
	}

	boolean nextOnProb(int factor) throws Exception
	{
		String nextState = currentState;

		if (isTerminated())
			return false;

		Enumeration colLabels = tpm.getColLabels(currentState);
		String colLabel = (String)colLabels.nextElement();
		nextState = colLabel;
		int percentProb = 0;
		int modFactor = 0;

		while(colLabels.hasMoreElements())
		{
			percentProb = (int)(tpm.getProbAt(currentState, colLabel) * 100);
			modFactor = factor % 101;

			if (modFactor <= percentProb)
				nextState = colLabel;

			colLabel = (String)colLabels.nextElement();
		}

		path.addLast(nextState);
		currentState = nextState;

		return true;
	}


	boolean isTerminated() throws Exception
	{
		return currentState.equals("Term");
	}

	List getTraversalPath() 
	{
		return path;
	}
}

class TestSuiteGeneratorFacade
{
	private final TestSuite ts;
	private final StateMachine sm;
	private final int numTestCasesToCreate;
	private Random numGen;

	private class CustomSeed extends GregorianCalendar
	{
		private static final long serialVersionUID = -8006528383988504720L;

		int getRandomSeed()
		{
			return (int)time;
		}
	}

	TestSuiteGeneratorFacade(final TestSuite ts, final StateMachine sm, int numTestCases) throws Exception
	{
		if (numTestCases < 0)
			throw new Exception("Number of test cases should be >= 0");

		this.ts = ts;
		this.numTestCasesToCreate = numTestCases;
		this.sm = sm;
		numGen = new Random(new CustomSeed().getRandomSeed());
	}

	void generate() throws Exception
	{
		for (int i = 1; i <= numTestCasesToCreate; i++)
		{
			while (sm.nextOnProb(numGen.nextInt()));

			ts.addTestCase(new TestCase(sm.getTraversalPath()));
			sm.restart();
		}
	}

}